/**
 * Software License, Version 1.0
 * 
 * Copyright 2008 The Trustees of Indiana University.  All rights reserved.
 * 
 *
 *Redistribution and use in source and binary forms, with or without 
 *modification, are permitted provided that the following conditions are met:
 *
 *1) All redistributions of source code must retain the above copyright notice,
 * the list of authors in the original source code, this list of conditions and
 * the disclaimer listed in this license;
 *2) All redistributions in binary form must reproduce the above copyright 
 * notice, this list of conditions and the disclaimer listed in this license in
 * the documentation and/or other materials provided with the distribution;
 *3) Any documentation included with all redistributions must include the 
 * following acknowledgment:
 *
 *"This product includes software developed by the Community Grids Lab. For 
 * further information contact the Community Grids Lab at 
 * http://communitygrids.iu.edu/."
 *
 * Alternatively, this acknowledgment may appear in the software itself, and 
 * wherever such third-party acknowledgments normally appear.
 * 
 *4) The name Indiana University or Community Grids Lab or Granules, 
 * shall not be used to endorse or promote products derived from this software 
 * without prior written permission from Indiana University.  For written 
 * permission, please contact the Advanced Research and Technology Institute 
 * ("ARTI") at 351 West 10th Street, Indianapolis, Indiana 46202.
 *5) Products derived from this software may not be called Granules, 
 * nor may Indiana University or Community Grids Lab or Granules appear
 * in their name, without prior written permission of ARTI.
 * 
 *
 * Indiana University provides no reassurances that the source code provided 
 * does not infringe the patent or any other intellectual property rights of 
 * any other entity.  Indiana University disclaims any liability to any 
 * recipient for claims brought by any other entity based on infringement of 
 * intellectual property rights or otherwise.  
 *
 * LICENSEE UNDERSTANDS THAT SOFTWARE IS PROVIDED "AS IS" FOR WHICH NO 
 * WARRANTIES AS TO CAPABILITIES OR ACCURACY ARE MADE. INDIANA UNIVERSITY GIVES
 * NO WARRANTIES AND MAKES NO REPRESENTATION THAT SOFTWARE IS FREE OF 
 * INFRINGEMENT OF THIRD PARTY PATENT, COPYRIGHT, OR OTHER PROPRIETARY RIGHTS. 
 * INDIANA UNIVERSITY MAKES NO WARRANTIES THAT SOFTWARE IS FREE FROM "BUGS", 
 * "VIRUSES", "TROJAN HORSES", "TRAP DOORS", "WORMS", OR OTHER HARMFUL CODE.  
 * LICENSEE ASSUMES THE ENTIRE RISK AS TO THE PERFORMANCE OF SOFTWARE AND/OR 
 * ASSOCIATED MATERIALS, AND TO THE PERFORMANCE AND VALIDITY OF INFORMATION 
 * GENERATED USING SOFTWARE.
 */
package cgl.granules.util;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.Enumeration;
import java.util.Properties;

/**
 * This is a utility class which includes methods for marshalling and
 * un-marshalling several commonly used data types.
 * 
 * @author Shrideep Pallickara
 * 
 */

public class MarshallingUtil {
	private static final MarshallingUtil instance = new MarshallingUtil();

	private MarshallingUtil() {
	}

	/**
	 * Retrieve the singleton instance of the <code>MarshallingUtil</code>
	 * 
	 * @return The singleton instance.
	 */
	public static MarshallingUtil getInstance() {
		return instance;
	}

	/**
	 * Converts a byte[] into a long[]
	 * 
	 * @param sequenceOfBytes
	 *            The byte[] input
	 * @return The long[] representation of the byte[]
	 * @throws IOException
	 *             If there are problems in the conversion process.
	 */
	public long[] convertByteArrayToLongArray(byte[] sequenceOfBytes)
			throws IOException {
		ByteArrayInputStream baInputStream = new ByteArrayInputStream(
				sequenceOfBytes);
		DataInputStream din = new DataInputStream(baInputStream);

		long[] sequenceOfLongs = new long[din.readInt()];
		for (int i = 0; i < sequenceOfLongs.length; i++) {
			sequenceOfLongs[i] = din.readLong();
		}
		return sequenceOfLongs;
	}

	/**
	 * Converts a long[] into a byte[].
	 * 
	 * @param sequenceOfLongs
	 *            The long[] input
	 * @return The byte[] representation of the long[].
	 * @throws IOException
	 *             If there are problems in the conversion process.
	 */
	public byte[] convertLongArrayToByteArray(long[] sequenceOfLongs)
			throws IOException {
		byte[] marshalledBytes = null;
		ByteArrayOutputStream baOutputStream = new ByteArrayOutputStream();
		DataOutputStream dout = new DataOutputStream(baOutputStream);

		dout.writeInt(sequenceOfLongs.length);
		for (int i = 0; i < sequenceOfLongs.length; i++) {
			dout.writeLong(sequenceOfLongs[i]);
		}

		dout.flush();
		marshalledBytes = baOutputStream.toByteArray();
		return marshalledBytes;
	}

	/**
	 * Writes a java.util.Properties object to the underlying DataOutputStream
	 * 
	 * @param dout
	 *            The instance of the <code>DataOutputStream</code>
	 * @param properties
	 *            The <code>Properties</code> instance that needs to be
	 *            serialized.
	 * @throws IOException
	 *             If there are problems in the marshalling process.
	 */
	public void writeProperties(DataOutputStream dout, Properties properties)
			throws IOException {
		checkDataOutputStream(dout);
		if (properties == null) {
			String errorReport = "Properties Object to be marshalled is NULL!";
			throw new IOException(errorReport);
		}
		int numOfProperties = properties.size();
		dout.writeShort(numOfProperties);

		if (numOfProperties == 0) {
			return;
		}

		int written = 0;
		Enumeration<?> e = properties.propertyNames();

		while (e.hasMoreElements()) {
			String key = (String) e.nextElement();
			String value = properties.getProperty(key);
			writeString(dout, key);
			writeString(dout, value);
			written++;
		}

		if (numOfProperties != written) {
			String errorReport = "Mismatch in the number of properties ("
					+ numOfProperties + ") and the number actually written ("
					+ written + ") to the underlying stream ";
			throw new IOException(errorReport);
		}

	}

	/**
	 * Reads a property object from the underlying DataInputStream
	 * 
	 * @param din
	 *            The instance of the <code>DataInputStream</code> where the
	 *            <code>Properties</code> instance will be read from.
	 * @return The underlying <code>Properties</code> instance
	 * @throws IOException
	 *             If there are problems in the unmarshalling process.
	 */
	public Properties readProperties(DataInputStream din) throws IOException {
		checkDataInputStream(din);
		Properties properties = new Properties();

		int numOfElements = din.readShort();
		for (int i = 0; i < numOfElements; i++) {
			String key = readString(din);
			String value = readString(din);
			properties.put(key, value);
		}

		return properties;
	}

	/**
	 * Writes a String to the underlying DataOutputStream
	 * 
	 * @param dout
	 *            The instance of the <code>DataOutputStream</code>
	 * @param element
	 *            The String that needs to be written
	 * @throws IOException
	 *             If there are problems in the Marshalling process.
	 */
	public void writeString(DataOutputStream dout, String element)
			throws IOException {
		if (element == null) {
			throw new IOException("Attempting to serialize NULL String");
		}

		byte[] elementBytes = element.getBytes();
		int elementLength = elementBytes.length;

		dout.writeInt(elementLength);
		dout.write(elementBytes);
	}

	/**
	 * Reads a String from the underlying DataInputStream
	 * 
	 * @param din
	 *            The instance of the <code>DataInputStream</code> where the
	 *            <code>String</code> will be read from.
	 * @return The underlying <code>String</code> instance
	 * @throws IOException
	 *             If there are problems in the un-marshalling process.
	 */
	public String readString(DataInputStream din) throws IOException {
		int elementLength = din.readInt();
		byte[] elementBytes = new byte[elementLength];
		din.readFully(elementBytes);

		String element = new String(elementBytes);
		return element;
	}

	/**
	 * Given a sequence of bytes, this method returns the int value associated
	 * with the sequence of bytes. This length sequence of bytes passed as the
	 * argument should satisfy the condition
	 * <code>(data.length - offset) >= 4</code>; otherwise, an illegal Argument
	 * exception will be thrown.
	 * 
	 * @param data
	 *            the byte[] which would be used to reconstruct the bytes
	 * @param offset
	 *            The point from which an attempt to reconstruct the integer
	 *            must be made
	 * @return The <code>int</code> value associated with the byte[] starting at
	 *         the specified offset.
	 */
	public int getInt(byte[] data, int offset) {
		if ((data.length - offset) < 4) {
			String errorReport = "The byte[] is of length =" + data.length
					+ ", and the specified offset is =" + offset
					+ ". This is insufficient to read an int (4 bytes)";
			throw new IllegalArgumentException(errorReport);
		}
		int w, x, y, z;
		w = data[0 + offset];
		x = data[1 + offset];
		y = data[2 + offset];
		z = data[3 + offset];
		if (w < 0)
			w += 256;
		if (x < 0)
			x += 256;
		if (y < 0)
			y += 256;
		if (z < 0)
			z += 256;
		return ((w << 24) + (x << 16) + (y << 8) + (z << 0));
	}

	/**
	 * Throws an exception if the specified DataInputStream is NULL
	 * 
	 * @param dout
	 * @throws IOException
	 */
	private void checkDataInputStream(DataInputStream din) throws IOException {
		if (din == null) {
			String errorReport = "DataInputStream to be used for un-marshalling is NULL";
			throw new IOException(errorReport);
		}
	}

	/**
	 * Throws an exception if the specified DataOutputStream is NULL
	 * 
	 * @param dout
	 * @throws IOException
	 */
	private void checkDataOutputStream(DataOutputStream dout)
			throws IOException {
		if (dout == null) {
			String errorReport = "DataOutputStream to be used for marshalling is NULL";
			throw new IOException(errorReport);
		}
	}
}
